﻿using CatClient.Configuration;
using CatClient.Message.Internals;
using CatClient.Message.Spi.IO;
using CatClient.Util;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Threading;

namespace CatClient.Message.Spi.Internals
{
    [Serializable]
    public class DefaultMessageManager : IMessageManager
    {
        public class Context
        {
            private readonly Stack<ITransaction> _mStack;

            private readonly IMessageTree _mTree;

            private readonly string _key;

            public string Key
            {
                get
                {
                    return this._key;
                }
            }

            public IMessageTree Tree
            {
                get
                {
                    return this._mTree;
                }
            }

            public Context(string domain, string hostName, string ipAddress, string key)
            {
                this._key = key;
                this._mTree = new DefaultMessageTree();
                this._mStack = new Stack<ITransaction>();
                Thread currentThread = Thread.CurrentThread;
                string friendlyName = Thread.GetDomain().FriendlyName;
                this._mTree.ThreadGroupName = friendlyName;
                this._mTree.ThreadId = currentThread.ManagedThreadId.ToString(CultureInfo.InvariantCulture);
                this._mTree.ThreadName = currentThread.Name;
                this._mTree.Domain = domain;
                this._mTree.HostName = hostName;
                this._mTree.IpAddress = ipAddress;
            }

            public Context(IMessageTree tree, string key)
            {
                this._key = key;
                this._mTree = tree;
                this._mStack = new Stack<ITransaction>();
            }

            public void Add(DefaultMessageManager manager, IMessage message)
            {
                if (this._mStack.Count == 0)
                {
                    IMessageTree messageTree = this._mTree.Copy();
                    messageTree.MessageId = manager.NextMessageId();
                    messageTree.Message = message;
                    manager.Flush(messageTree);
                    return;
                }
                this._mStack.Peek().AddChild(message);
            }

            public bool End(DefaultMessageManager manager, ITransaction transaction)
            {
                if (this._mStack.Count != 0)
                {
                    ITransaction transaction2 = this._mStack.Pop();
                    if (transaction == transaction2)
                    {
                        this.ValidateTransaction(manager, (this._mStack.Count == 0) ? null : this._mStack.Peek(), transaction2);
                    }
                    else
                    {
                        while (transaction != transaction2 && this._mStack.Count != 0)
                        {
                            this.ValidateTransaction(manager, this._mStack.Peek(), transaction2);
                            transaction2 = this._mStack.Pop();
                        }
                    }
                    if (this._mStack.Count == 0)
                    {
                        IMessageTree tree = this._mTree.Copy();
                        this._mTree.MessageId = null;
                        this._mTree.Message = null;
                        manager.Flush(tree);
                        return true;
                    }
                }
                return false;
            }

            public ITransaction PeekTransaction()
            {
                if (this._mStack.Count != 0)
                {
                    return this._mStack.Peek();
                }
                return null;
            }

            public void Start(DefaultMessageManager manager, ITransaction transaction, bool forked)
            {
                if (this._mStack.Count != 0)
                {
                    if (!(transaction is IForkedTransaction))
                    {
                        ITransaction transaction2 = this._mStack.Peek();
                        this.AddTransactionChild(manager, transaction, transaction2);
                    }
                }
                else
                {
                    this._mTree.Message = transaction;
                }
                if (!forked)
                {
                    this._mStack.Push(transaction);
                }
            }

            internal void LinkAsRunAway(DefaultMessageManager manager, IForkedTransaction transaction)
            {
                AbstractMessage abstractMessage = new DefaultEvent("RemoteCall", "RunAway", null);
                abstractMessage.AddData(transaction.ForkedMessageId, string.Format("{0}:{1}", transaction.Type, transaction.Name));
                abstractMessage.Timestamp = transaction.Timestamp;
                abstractMessage.Status = "0";
                abstractMessage.SetCompleted(true);
                transaction.Standalone = true;
                manager.Add(abstractMessage);
            }

            private void MarkAsRunAway(ITransaction parent, DefaultTaggedTransaction transaction)
            {
                if (!transaction.HasChildren())
                {
                    transaction.AddData("RunAway");
                }
                transaction.Status = "0";
                transaction.Standalone = true;
                transaction.SetCompleted(true);
            }

            private void MarkAsNotCompleted(DefaultTransaction transaction)
            {
                AbstractMessage abstractMessage = new DefaultEvent("CAT", "BadInstrument", null)
                {
                    Status = "TransactionNotCompleted"
                };
                abstractMessage.SetCompleted(true);
                transaction.AddChild(abstractMessage);
                transaction.SetCompleted(true);
            }

            internal void ValidateTransaction(DefaultMessageManager manager, ITransaction parent, ITransaction transaction)
            {
                if (transaction.Standalone)
                {
                    IList<IMessage> children = transaction.Children;
                    int count = children.Count;
                    for (int i = 0; i < count; i++)
                    {
                        IMessage message = children[i];
                        if (message is ITransaction)
                        {
                            this.ValidateTransaction(manager, transaction, message as ITransaction);
                        }
                    }
                    if (!transaction.IsCompleted() && transaction is DefaultTransaction)
                    {
                        this.MarkAsNotCompleted(transaction as DefaultTransaction);
                        return;
                    }
                    if (!transaction.IsCompleted())
                    {
                        if (transaction is IForkedTransaction)
                        {
                            this.LinkAsRunAway(manager, transaction as IForkedTransaction);
                            return;
                        }
                        if (transaction is DefaultTaggedTransaction)
                        {
                            this.MarkAsRunAway(parent, transaction as DefaultTaggedTransaction);
                        }
                    }
                }
            }

            private void AddTransactionChild(DefaultMessageManager manager, IMessage message, ITransaction transaction)
            {
                long arg_2B_0 = this.TrimToHour(this._mTree.Message.Timestamp);
                long num = this.TrimToHour(message.Timestamp - 10000L);
                if (arg_2B_0 < num)
                {
                    this.TruncateAndFlush(manager, message.Timestamp);
                }
                transaction.AddChild(message);
            }

            private void TruncateAndFlush(DefaultMessageManager manager, long timestamp)
            {
                IMessageTree mTree = this._mTree;
                Stack<ITransaction> mStack = this._mStack;
                IMessage message = mTree.Message;
                if (message is DefaultTransaction)
                {
                    string text = mTree.MessageId;
                    if (text == null)
                    {
                        text = manager.NextMessageId();
                        mTree.MessageId = text;
                    }
                    string rootMessageId = mTree.RootMessageId;
                    string text2 = manager.NextMessageId();
                    DefaultTransaction defaultTransaction = message as DefaultTransaction;
                    DefaultTransaction defaultTransaction2 = new DefaultTransaction(defaultTransaction.Type, defaultTransaction.Name, manager);
                    defaultTransaction2.Timestamp = defaultTransaction.Timestamp;
                    defaultTransaction2.DurationInMicros = defaultTransaction.DurationInMicros;
                    defaultTransaction2.AddData(defaultTransaction.Data);
                    defaultTransaction2.Status = "0";
                    this.MigrateMessage(manager, mStack, defaultTransaction, defaultTransaction2, 1);
                    List<ITransaction> list = mStack.ToList<ITransaction>();
                    for (int i = list.Count - 1; i >= 0; i--)
                    {
                        DefaultTransaction expr_CC = list[i] as DefaultTransaction;
                        expr_CC.Timestamp = timestamp;
                        expr_CC.DurationInMicros = -1L;
                    }
                    IEvent @event = new DefaultEvent("RemoteCall", "Next", null);
                    @event.AddData(text2);
                    @event.Status = "0";
                    defaultTransaction2.AddChild(@event);
                    IMessageTree messageTree = mTree.Copy();
                    messageTree.Message = defaultTransaction2;
                    this._mTree.MessageId = text2;
                    this._mTree.ParentMessageId = text;
                    this._mTree.RootMessageId = (rootMessageId ?? text);
                    manager.Flush(messageTree);
                }
            }

            private void MigrateMessage(DefaultMessageManager manager, Stack<ITransaction> stack, ITransaction source, ITransaction target, int level)
            {
                ITransaction transaction = (level < stack.Count) ? stack.ToList<ITransaction>()[level] : null;
                bool flag = false;
                foreach (IMessage current in source.Children)
                {
                    if (current != transaction)
                    {
                        target.AddChild(current);
                    }
                    else
                    {
                        DefaultTransaction defaultTransaction = new DefaultTransaction(transaction.Type, transaction.Name, manager);
                        defaultTransaction.Timestamp = transaction.Timestamp;
                        defaultTransaction.DurationInMicros = transaction.DurationInMicros;
                        defaultTransaction.AddData(transaction.Data);
                        defaultTransaction.Status = "0";
                        target.AddChild(defaultTransaction);
                        this.MigrateMessage(manager, stack, transaction, defaultTransaction, level + 1);
                        flag = true;
                    }
                }
                source.Children.Clear();
                if (flag)
                {
                    source.AddChild(transaction);
                }
            }

            private long TrimToHour(long timestamp)
            {
                return timestamp - timestamp % 3600000L;
            }
        }

        [Serializable]
        public class CurrentKey
        {
            public string Current
            {
                get;
                set;
            }

            public object Deep
            {
                get;
                set;
            }

            public string Second
            {
                get;
                set;
            }
        }

        private ClientConfig _clientConfig;

        private MessageIdFactory _factory;

        private bool _firstMessage = true;

        private string _hostName;

        private IMessageSender _sender;

        private IMessageStatistics _statistics;

        private StatusUpdateTask _statusUpdateTask;

        private ConcurrentDictionary<string, ITaggedTransaction> _taggedTransactions;

        private long _currentContextCatNullMsgLogCount;

        private long _setCurrentKeyNullCount;

        public DefaultMessageManager.Context CurrentContext
        {
            get
            {
                DefaultMessageManager.Context context = null;
                int num = 0;
                object data = CallContext.GetData("(^T_T^)");
                if (data != null)
                {
                    num = (int)data;
                }
                string text = (string)CallContext.GetData(string.Format("{0}+{1}", "(^T_T^)", num));
                if (text != null)
                {
                    context = (DefaultMessageManager.Context)CallContext.LogicalGetData(text);
                }
                if (context != null)
                {
                    return context;
                }
                string text2 = (string)CallContext.GetData("/(@ >_< @)/");
                if (text2 != null)
                {
                    context = (DefaultMessageManager.Context)CallContext.LogicalGetData(text2);
                }
                if (context != null)
                {
                    return context;
                }
                context = (DefaultMessageManager.Context)CallContext.LogicalGetData("(*`-_-`*)");
                if (context != null && CallContext.GetData("(¬_¬)ﾉignore") == null)
                {
                    bool flag = true;
                    if (!CatConfigManager.GetConfigObject<bool>("CatNullMsgLog", false))
                    {
                        flag = (Interlocked.Increment(ref this._currentContextCatNullMsgLogCount) % (long)CatConfigManager.GetConfigObject<int>("DefaultLogCount", 1000) == 0L);
                    }
                    if (flag)
                    {
                        LogInfoWriter.GetInstance("Cat", 0, LogInfoWriter.LogLayout.Default).Error("CatNullMsg Context Cross: " + Environment.StackTrace);
                    }
                }
                return null;
            }
            set
            {
                CallContext.LogicalSetData(value.Key, value);
            }
        }

        public virtual ClientConfig ClientConfig
        {
            get
            {
                return this._clientConfig;
            }
        }

        public virtual ITransaction PeekTransaction
        {
            get
            {
                if (this.GetContext() == null)
                {
                    this.Setup();
                }
                return this.CurrentContext.PeekTransaction();
            }
        }

        public virtual IMessageTree ThreadLocalMessageTree
        {
            get
            {
                if (this.CurrentContext == null)
                {
                    this.Setup();
                }
                return this.CurrentContext.Tree;
            }
        }

        public virtual bool CatEnabled
        {
            get
            {
                return this._clientConfig.Domain.Enabled && this.CurrentContext != null;
            }
        }

        internal void DisposeContext()
        {
            this.DisposeContext(this.CurrentContext);
        }

        internal void DisposeContext(DefaultMessageManager.Context context)
        {
            CallContext.FreeNamedDataSlot("/(@ >_< @)/");
            if (context != null)
            {
                CallContext.FreeNamedDataSlot(context.Key);
            }
        }

        public virtual void Reset()
        {
        }

        public virtual void InitializeClient(ClientConfig clientConfig)
        {
            if (clientConfig == null)
            {
                throw new ArgumentException("cat ClientConfig can not be null");
            }
            this._clientConfig = clientConfig;
            this._hostName = NetworkInterfaceManager.GetLocalHostName();
            this._statistics = new DefaultMessageStatistics();
            this._sender = new TcpMessageSender(this._clientConfig, this._statistics);
            this._sender.Initialize();
            this._factory = new MessageIdFactory();
            this._taggedTransactions = new ConcurrentDictionary<string, ITaggedTransaction>();
            this._factory.Initialize(this._clientConfig.Domain.Id);
            if (CatConfigManager.GetConfigValue("CatHeartbeatEnabled", "true") == "true")
            {
                this._statusUpdateTask = new StatusUpdateTask(this._statistics);
                ThreadPool.QueueUserWorkItem(new WaitCallback(this._statusUpdateTask.Run));
                LogInfoWriter.GetInstance("Cat", 0, LogInfoWriter.LogLayout.Default).Info("Thread(StatusUpdateTask) started.");
            }
        }

        public virtual bool HasContext()
        {
            return this.CurrentContext != null;
        }

        public virtual void Add(IMessage message)
        {
            DefaultMessageManager.Context context = this.GetContext();
            if (context == null)
                return;
            
            context.Add(this, message);
        }

        public void Bind(string tag, string title)
        {
            ITaggedTransaction taggedTransaction = null;
            if (this._taggedTransactions.TryGetValue(tag, out taggedTransaction))
            {
                IMessageTree threadLocalMessageTree = this.ThreadLocalMessageTree;
                if (threadLocalMessageTree != null)
                {
                    if (threadLocalMessageTree.MessageId == null)
                    {
                        threadLocalMessageTree.MessageId = this.NextMessageId();
                    }
                    taggedTransaction.Start();
                    taggedTransaction.Bind(tag, threadLocalMessageTree.MessageId, title);
                }
            }
        }

        public virtual void Setup()
        {
            this.Setup("(*`-_-`*)");
        }

        public void Setup(string contextKey)
        {
            this.Setup("/(@ >_< @)/", contextKey);
        }

        public void SecondSetup(string contextKey)
        {
            int num = 0;
            object data = CallContext.GetData("(^T_T^)");
            if (data != null)
                num = (int)data + 1;
            
            CallContext.SetData("(^T_T^)", num);
            this.Setup(string.Format("{0}+{1}", "(^T_T^)", num), contextKey);
        }

        private void Setup(string key, string contextKey)
        {
            string localHostAddress = NetworkInterfaceManager.GetLocalHostAddress();
            DefaultMessageManager.Context context = new DefaultMessageManager.Context(this._clientConfig.Domain.Id, this._hostName, localHostAddress, contextKey);
            CallContext.SetData(key, contextKey);
            CallContext.LogicalSetData(context.Key, context);
        }

        public void Setup(IMessageTree tree)
        {
            DefaultMessageManager.Context context = new DefaultMessageManager.Context(tree, "(*`-_-`*)");
            CallContext.SetData("/(@ >_< @)/", context.Key);
            CallContext.LogicalSetData(context.Key, context);
        }

        public void SetMainContextToCurrent(bool force)
        {
            CallContext.SetData("(¬_¬)ﾉignore", 1);
            if (!force && this.CurrentContext != null)
            {
                CallContext.FreeNamedDataSlot("(¬_¬)ﾉignore");
                return;
            }
            CallContext.FreeNamedDataSlot("(¬_¬)ﾉignore");
            DefaultMessageManager.Context context = (DefaultMessageManager.Context)CallContext.LogicalGetData("(*`-_-`*)");
            if (context != null)
            {
                CallContext.SetData("/(@ >_< @)/", context.Key);
            }
        }

        public void SetCurrent(string contextKey)
        {
            CallContext.SetData("/(@ >_< @)/", contextKey);
        }

        public void FreeSecondKey()
        {
            int num = 0;
            object data = CallContext.GetData("(^T_T^)");
            if (data != null)
            {
                num = (int)data;
                if (num == 0)
                {
                    CallContext.FreeNamedDataSlot("(^T_T^)");
                }
                else
                {
                    CallContext.SetData("(^T_T^)", num - 1);
                }
            }
            CallContext.FreeNamedDataSlot(string.Format("{0}+{1}", "(^T_T^)", num));
        }

        public DefaultMessageManager.CurrentKey GetCurrent()
        {
            object data = CallContext.GetData("(^T_T^)");
            return new DefaultMessageManager.CurrentKey
            {
                Current = (string)CallContext.GetData("/(@ >_< @)/"),
                Deep = data,
                Second = (data == null) ? null : ((string)CallContext.GetData(string.Format("{0}+{1}", "(^T_T^)", data)))
            };
        }

        public void SetCurrent(DefaultMessageManager.CurrentKey current)
        {
            if (current == null)
            {
                bool flag = true;
                if (!CatConfigManager.GetConfigObject<bool>("CurrentKeyCheck", false))
                {
                    flag = (Interlocked.Increment(ref this._setCurrentKeyNullCount) % (long)CatConfigManager.GetConfigObject<int>("DefaultLogCount", 1000) == 0L);
                }
                if (flag)
                {
                    LogInfoWriter.GetInstance("Cat", 0, LogInfoWriter.LogLayout.Default).Error("SetCurrent null: " + Environment.StackTrace);
                }
                return;
            }
            if (!string.IsNullOrEmpty(current.Current))
            {
                CallContext.SetData("/(@ >_< @)/", current.Current);
            }
            if (current.Deep != null)
            {
                CallContext.SetData("(^T_T^)", current.Deep);
                CallContext.SetData(string.Format("{0}+{1}", "(^T_T^)", current.Deep), current.Second);
            }
        }

        public virtual void Start(ITransaction transaction, bool forked)
        {
            DefaultMessageManager.Context context = this.GetContext();
            if (context != null)
            {
                context.Start(this, transaction, forked);
                if (transaction is DefaultTaggedTransaction)
                {
                    ITaggedTransaction taggedTransaction = transaction as DefaultTaggedTransaction;
                    this._taggedTransactions[taggedTransaction.Tag] = taggedTransaction;
                    return;
                }
            }
            else if (this._firstMessage)
            {
                this._firstMessage = false;
                LogInfoWriter.GetInstance("Cat", 0, LogInfoWriter.LogLayout.Default).Warn("CAT client is not enabled because it's not initialized yet");
            }
        }

        public virtual void End(ITransaction transaction)
        {
            DefaultMessageManager.Context context = this.GetContext(transaction.ContextKey);
            if (context != null && transaction.Standalone && context.End(this, transaction))
            {
                this.DisposeContext(context);
            }
        }

        public MessageIdFactory GetMessageIdFactory()
        {
            return this._factory;
        }

        internal void Flush(IMessageTree tree)
        {
            if (tree.MessageId == null)
            {
                tree.MessageId = this.NextMessageId();
            }
            if (this._sender != null)
            {
                this._sender.Send(tree);
                IMessageStatistics messageStatistics = this._statistics;
                if (messageStatistics == null)
                    return;
                
                messageStatistics.OnSending();
            }
        }

        internal DefaultMessageManager.Context GetContext()
        {
            if (Cat.IsInitialized())
            {
                return this.CurrentContext;
            }
            return null;
        }

        internal DefaultMessageManager.Context GetContext(string contextKey)
        {
            if (contextKey == null)
            {
                LogInfoWriter.GetInstance("", 0, LogInfoWriter.LogLayout.Default).Warn("context key is null!");
                return null;
            }
            return (DefaultMessageManager.Context)CallContext.LogicalGetData(contextKey);
        }

        internal void Clear()
        {
            TcpMessageSender messageSender = this._sender as TcpMessageSender;
            if (messageSender == null)
                return;
            
            messageSender.Clear();
        }

        public void LinkAsRunAway(IForkedTransaction transaction)
        {
            DefaultMessageManager.Context context = this.GetContext();
            if (context == null)
                return;

            context.LinkAsRunAway(this, transaction);
        }

        public string NextMessageId()
        {
            return this._factory.GetNextId();
        }
    }
}
